//===============================================================================
// Q4Tech Engineering Team
// Mobile Updater Application Block - November 2006
//===============================================================================
// Copyright  Q4Tech Informatica Argentina.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using Microsoft.ApplicationBlocks.Mobile.Updater;
using Microsoft.Practices.Mobile.ConnectionMonitor;
using Q4Tech.Engineering.UpdaterAgent.Common;
using Q4Tech.Engineering.UpdaterAgent.Configuration;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.ApplicationBlocks.Mobile.Updater.Utilities;
using Q4Tech.Engineering.UpdaterAgent.DeviceIdProvider;

namespace Q4Tech.Engineering.UpdaterAgent.Management
{
    internal class ManifestListManager : IDisposable
    {
        #region Events

        public event InfoAvailableEventHandler InfoAvailable;

        public event DownloadCompleteEventHandler DownloadComplete;

        public event ActivationStartedEventHandler ActivationStarted;

        public event ActivationSuspendedEventHandler ActivationSuspended;

        public event ActivationCompletedEventHandler ActivationCompleted;

        public event CheckForUpdatesStartedEventHandler CheckForUpdatesStarted;

        public event CheckForUpdatesFinishedEventHandler CheckForUpdatesFinished;

        public event UpdateSuspendedEventHandler UpdateSuspended;

        public event PendingUpdateResumedEventHandler PendingUpdateResumed;

        public event UpdateErrorEventHandler UpdateError;

        public event ConnectionStatusChangedEventHandler ConnectionStatusChanged;

        #endregion

        #region Fields

        private IDeviceIdProvider idProvider;

        private NotificationManager notifierServer = new NotificationManager();

        private UpdaterConfigurationSection section;

        private ConnectionMonitor monitor;

        private int pendingDownloads = 0;

        #endregion

        public ManifestListManager()
        {
            ApplicationUpdaterManager.Initialize(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase), new DownloaderFactory());

            section = UpdaterConfigurationSection.GetSection();

            if (section == null)
                throw new ManifestListManagerException("Invalid config section.");

            foreach (DeviceIdProviderElement element in section.DeviceIdProviders)
            {
                if (element.Name == section.DefaultDeviceIdProvider)
                {
                    idProvider = (IDeviceIdProvider)Activator.CreateInstance(element.Type);
                    break;
                }
            }

            monitor = ConnectionMonitorFactory.CreateFromConfiguration("Connections");

            monitor.ConnectionStatusChanged += new EventHandler(monitor_ConnectionStatusChanged);
        }

        public string DeviceId
        {
            get
            {
                return idProvider.GetDeviceId();
            }
        }

        public bool IsConnected
        {
            get
            {
                return monitor.IsConnected;
            }
        }

        private void PublishInfo(String info, bool trivial)
        {
            if (InfoAvailable != null)
                InfoAvailable(this, new InfoAvailableEventArgs(info, trivial));
        }

        private XSD.ManifestList GetManifestList()
        {
            UpdaterServer.Service service = new UpdaterServer.Service();

            service.Url = section.ManifestListUri;

            PublishInfo(String.Format("Calling {0} ...", service.Url), true);

            string deviceId = idProvider.GetDeviceId();

            XmlNode doc = service.GetManifestList(deviceId);

            XmlSerializer serializer = new XmlSerializer(typeof(XSD.ManifestList));

            return (XSD.ManifestList)serializer.Deserialize(new StringReader(doc.OuterXml));
        }

        public void ForceCheckForUpdates()
        {
            CheckForUpdates(true);
        }

        private void CheckForUpdates(bool forced)
        {
            if (CheckForUpdatesStarted != null)
                CheckForUpdatesStarted(this, new CheckForUpdatesStartedEventArgs(!forced));

            try
            {
                XSD.ManifestList manifestList = GetManifestList();

                if (manifestList == null)
                    throw new ManifestListManagerException("Incorrect ManifestList format");

                if (manifestList.Manifests.Count > 0)
                    PublishInfo(String.Format("Processing {0} applications", manifestList.Manifests.Count), false);

                pendingDownloads = 0;

                // Start an update manager for each application and manifest.
                foreach (XSD.Manifest manifestListManifest in manifestList.Manifests)
                {
                    PublishInfo(String.Format("Getting update manager for {0}", manifestListManifest.ApplicationId), true);

                    ApplicationUpdaterManager manager = ApplicationUpdaterManager.GetUpdater(manifestListManifest.ApplicationId);

                    manager.UpdatesSuspended -= manager_UpdatesSuspended;
                    manager.UpdatesSuspended += manager_UpdatesSuspended;

                    manager.ActivationSuspended -= manager_ActivationSuspended;
                    manager.ActivationSuspended += manager_ActivationSuspended;

                    manager.PendingUpdatesDetected -= manager_PendingUpdatesDetected;
                    manager.PendingUpdatesDetected += manager_PendingUpdatesDetected;

                    manager.ActivationCompleted -= manager_ActivationCompleted;
                    manager.ActivationCompleted += manager_ActivationCompleted;

                    manager.ActivationStarted -= manager_ActivationStarted;
                    manager.ActivationStarted += manager_ActivationStarted;

                    PublishInfo(String.Format("Checking for updates from {0} ...", manifestListManifest.ManifestUri), true);

                    Manifest[] manifests = manager.CheckForUpdates(new Uri(manifestListManifest.ManifestUri));

                    int applyingManifestCount = 0;

                    foreach (Manifest manifest in manifests)
                    {
                        if (manifest.Apply)
                            applyingManifestCount++;
                    }

                    PublishInfo(String.Format("Found {0} updates", manifests.Length), true);

                    if (applyingManifestCount < manifests.Length)
                    {
                        if (applyingManifestCount == 0)
                            PublishInfo(String.Format("No updates will be applied.", manifests.Length - applyingManifestCount), true);
                        else if (manifests.Length - applyingManifestCount > 1)
                            PublishInfo(String.Format("{0} updates won't be applied.", manifests.Length - applyingManifestCount), true);
                        else
                            PublishInfo("1 update won't be applied.", true);
                    }

                    if (applyingManifestCount > 0)
                    {
                        if (!notifierServer.RaiseUpdatesAvailable(manifestListManifest.ApplicationId))
                            continue;

                        foreach (Manifest manifest in manifests)
                        {
                            if (!String.IsNullOrEmpty(manifestListManifest.Downloader))
                            {
                                if (manifest.Downloader != null)
                                    manifest.Downloader.Name = manifestListManifest.Downloader;
                            }
                        }

                        // Download and apply for each application.
                        Interlocked.Increment(ref pendingDownloads);



                        PublishInfo(String.Format("Downloading {0} binaries from manifest...", manifests.Length), true);
                        manager.BeginDownload(manifests);

                        // Use async so that we can continue updating other apps.
                        manager.DownloadCompleted -= manager_DownloadCompleted;
                        manager.DownloadCompleted += manager_DownloadCompleted;
                    }
                }

                if (CheckForUpdatesFinished != null)
                    CheckForUpdatesFinished(this, new CheckForUpdatesFinishedEventArgs(!forced));
            }
            catch (WebException ex)
            {
                if (UpdateError != null)
                    UpdateError(this, new UpdateErrorEventArgs(null, String.Format("WebService error: {0}", ex.Message), ex));
                else
                    throw new ManifestListManagerException("Unexpected error", ex);
            }
            catch (Exception e)
            {
                if (UpdateError != null)
                    UpdateError(this, new UpdateErrorEventArgs(null, e.Message, e));
                else
                    throw new ManifestListManagerException("Unexpected error", e);
            }
        }

        void manager_ActivationCompleted(object sender, Microsoft.ApplicationBlocks.Mobile.Updater.ActivationCompleteEventArgs e)
        {
            if (ActivationCompleted != null)
                ActivationCompleted(this, new ActivationCompletedEventArgs(e.Manifest, e.Success));
        }

        void manager_PendingUpdatesDetected(object sender, PendingUpdatesDetectedEventArgs e)
        {
            if (PendingUpdateResumed != null)
                PendingUpdateResumed(this, new PendingUpdateResumedEventArgs(e.Manifests));

            ((ApplicationUpdaterManager)sender).ResumePendingUpdates();
        }

        void manager_ActivationSuspended(object sender, Microsoft.ApplicationBlocks.Mobile.Updater.ActivationSuspendedEventArgs e)
        {
            if (ActivationSuspended != null)
                ActivationSuspended(this, new ActivationSuspendedEventArgs(e.Manifest, e.Reason));
        }

        void manager_UpdatesSuspended(object sender, Microsoft.ApplicationBlocks.Mobile.Updater.UpdatesSuspendedEventArgs e)
        {
            if (UpdateSuspended != null)
                UpdateSuspended(this, new UpdateSuspendedEventArgs(e.Manifest, e.Reason));
        }

        void monitor_ConnectionStatusChanged(object sender, EventArgs e)
        {
            if (ConnectionStatusChanged != null)
                ConnectionStatusChanged(this, new EventArgs());

            if (monitor.IsConnected)
                CheckForUpdates(false);
        }

        private void manager_DownloadCompleted(object sender, ManifestEventArgs args)
        {
            Interlocked.Decrement(ref pendingDownloads);

            if (DownloadComplete != null)
            {
                DownloadCompleteEventArgs cargs = new DownloadCompleteEventArgs(args.Manifest);

                DownloadComplete(this, cargs);
            }

            // Activate may cause tasks to execute.
            if (args.Manifest.Apply)
                ((ApplicationUpdaterManager)sender).Activate(new Manifest[] { args.Manifest });
        }

        private bool manager_ActivationStarted(object sender, ManifestEventArgs args)
        {
            if (ActivationStarted != null)
                ActivationStarted(this, new ActivationStartedEventArgs(args.Manifest));

            return notifierServer.RaiseUpdateInProcess(args.Manifest.Application.ApplicationId);
        }

        public int PendingDownloads
        {
            get
            {
                return pendingDownloads;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            notifierServer.Dispose();
        }

        #endregion
    }
}
